#ifdef _WIN32

#include "FileStream.h"
#include "Exceptions.win32.h"
#include "Format.h"

namespace lhcutils
{

FileStream::FileStream(const UnicodeString& filePath, FileOpenMode::Value mode, int fileAccess) : m_fileAccess(fileAccess)
{
	boost::shared_ptr<const CountedArray<wchar_t> > ntWFilePath = filePath.ToWinStrNullTerminated();

	LPCWSTR lpFileName = ntWFilePath->Ptr;
	DWORD dwDesiredAccess = FileAccessAsDword(fileAccess);
	DWORD dwShareMode = FILE_SHARE_READ;
	LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL;
	DWORD dwCreationDisposition = ModeAsDword(mode);
	DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
	HANDLE hTemplateFile = NULL;

	/*if(mode == fileopen_append_or_create)
	{
		dwDesiredAccess = FILE_APPEND_DATA;
	}*/
	// That would make it so the caller can *only* append

	m_handle = CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition,
		dwFlagsAndAttributes, hTemplateFile);

	if(m_handle == INVALID_HANDLE_VALUE)
	{
		DWORD errorCode = GetLastError();
		THROW_WIN32_DERIVED_IO(Format(*ustr("Could not open {0}."), filePath));
	}

	try
	{
		m_fileType = GetFileType(m_handle);
		if(m_fileType == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR)
		{
			THROW_WIN32IO(Format(*ustr("Could not open {0}."), filePath));
		}

		if(mode == FileOpenMode::AppendOrCreate)
		{
			// Seek to the end of the file if the caller wants to append
			SetPosition(0, SeekOrigin::End);
		}
	}
	catch(...)
	{
		BOOL success = CloseHandle(m_handle);
	}
}

FileStream::FileStream(HANDLE handle, int fileAccess) : m_handle(handle), m_fileAccess(fileAccess)
{
	m_fileType = GetFileType(m_handle);
	if(m_fileType == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR)
	{
		BOOL success = CloseHandle(m_handle);
		THROW_WIN32IO(ustr("Could not initialize file stream from handle."));
	}
}

DWORD FileStream::FileAccessAsDword(int fileAccess)
{
	DWORD dwDesiredAccess = 0;
	if((fileAccess & FileAccess::Read) == FileAccess::Read)
	{
		dwDesiredAccess |= GENERIC_READ;
	}
	if((fileAccess & FileAccess::Write) == FileAccess::Write)
	{
		dwDesiredAccess |= GENERIC_WRITE;
	}

	return dwDesiredAccess;
}

DWORD FileStream::ModeAsDword(FileOpenMode::Value mode)
{
	switch(mode)
	{
	case FileOpenMode::CreateNew:
		return CREATE_NEW;
		break;
	case FileOpenMode::CreateOrTruncate:
		return CREATE_ALWAYS;
		break;
	case FileOpenMode::OpenExisting:
		return OPEN_EXISTING;
		break;
	case FileOpenMode::OpenOrCreate:
		return OPEN_ALWAYS;
		break;
	case FileOpenMode::Truncate:
		return TRUNCATE_EXISTING;
		break;
	case FileOpenMode::AppendOrCreate:
		return OPEN_ALWAYS; // Need to handle this mode specially: Use FILE_APPEND_DATA for file access.
		break;
	}
}

void FileStream::Close()
{
	BOOL success = CloseHandle(m_handle);
	if(success == 0)
	{
		THROW_WIN32IO(ustr("Error closing file."));
	}
}

FileStream::~FileStream()
{
	BOOL success = CloseHandle(m_handle);
	// Can't really do anything if the close fails
}

HANDLE FileStream::GetWin32Handle()
{
	return m_handle;
}

bool FileStream::CanRead()
{
	return (m_fileAccess & FileAccess::Read) == FileAccess::Read;
}

bool FileStream::CanWrite()
{
	return (m_fileAccess & FileAccess::Write) == FileAccess::Write;
}

bool FileStream::CanSeek()
{
	return m_fileType == FILE_TYPE_DISK;
}

int64_t FileStream::GetPosition()
{
	LARGE_INTEGER position;
	BOOL success = SetFilePointerEx(m_handle, 0, &position, FILE_CURRENT);
	if(success == 0)
	{
		THROW_WIN32IO(ustr("Could not get the current position of the stream."));
	}
	return position.QuadPart;
}

void FileStream::SetPosition(int64_t newPosition, SeekOrigin::Value origin)
{
	LARGE_INTEGER position; position.QuadPart = newPosition;
	BOOL success = SetFilePointerEx(m_handle, position, NULL, SeekOriginAsDword(origin));
	if(success == 0)
	{
		THROW_WIN32IO(ustr("Could not set the current position of the stream."));
	}
}

DWORD FileStream::SeekOriginAsDword(SeekOrigin::Value origin)
{
	// TODO: Move this somewhere else?
	if(origin == SeekOrigin::Begin)
	{
		return FILE_BEGIN;
	}
	else if(origin == SeekOrigin::Current)
	{
		return FILE_CURRENT;
	}
	else
	{
		return FILE_END;
	}
	// TODO: Throw an exception if it's none of these?
}

void FileStream::Flush()
{
	BOOL success = FlushFileBuffers(m_handle);
	if(success == 0)
	{
		THROW_WIN32IO(ustr("Error while flushing buffered file output."));
	}
}

size_t FileStream::Read(void* buffer, size_t count)
{
	DWORD numBytesRead;
	BOOL success = ReadFile(m_handle, buffer, count, &numBytesRead, NULL);
	if(success == 0)
	{
		THROW_WIN32IO(ustr("Error while reading file."));
	}
	return numBytesRead;
}

void FileStream::Write(const void* buffer, size_t count)
{
	const uint8_t* byteBuffer = reinterpret_cast<const uint8_t*>(buffer);
	DWORD totalBytesWritten = 0;
	while(totalBytesWritten < count)
	{
		DWORD bytesWritten;
		DWORD bytesToWrite = count - totalBytesWritten;
		BOOL success = WriteFile(m_handle, byteBuffer + totalBytesWritten, bytesToWrite, &bytesWritten, NULL);
		if(success == 0)
		{
			THROW_WIN32IO(ustr("Error while writing to file."));
		}
		totalBytesWritten += bytesWritten;
	}
}
#endif

/*
 Copyright 2011 Greg Najda

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
